/* Created July 23, 2006.
 * Copyright (c) 2006, George Wright (geo@loyola.edu)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright notice, 
 *       this list of conditions and the following disclaimer.
 * 
 *     * Redistributions in binary form must reproduce the above copyright 
 *       notice, this list of conditions and the following disclaimer in the 
 *       documentation and/or other materials provided with the distribution.
 * 
 *     * Neither the name of George Wright nor the name of Loyola College
 *       may be used to endorse or promote products derived from this software 
 *       without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * To learn more about open source licenses, please visit: 
 * http://opensource.org/index.php
 * 
 * Changed by Vladimir Iglovikov
 */
package common;

import java.util.ArrayList;
import java.util.Iterator;

/**
 * A time series. All routines assume the series does not contain any missing
 * observations. Missing values should be replaced with estimates.
 * <p>
 * This encapsulation of time series data was designed on the assumption that it
 * would be instantiated only reading in an array of <code>double</code> values
 * from a disk file. That assumption isn't enforced in any way by this code, but
 * constructors are limited to one with a <code>double[]</code> argument.
 * <p>
 * $Id: Series.java,v 1.1 2008/07/02 12:48:56 Geo Exp $
 * </p>
 */
public class Series {
	/**
	 * For labeling diagnostics.
	 */
	protected final static String ID = "Series.";

	/**
	 * Array of the originalSeries' autocorrelation values.
	 */
	protected double[] autocorrelation = null;

	/**
	 * The confidence limit for the originalSeries' autocorrelation values.
	 */
	protected double autocorrelationConfidenceLimit = 0.0;

	/**
	 * Array of the originalSeries' autocovariance values.
	 */
	protected double[] autocovariance = null;

	/**
	 * <code>ArrayList</code> of the originalSeries' differences.
	 */
	ArrayList<Difference> difference = null;

	/**
	 * Index (into the <code>originalValue</code> array) of the first
	 * originalValue used in forecasting. This allows the user to trim the
	 * originalSeries to the apparent beginning of a periodic cycle.
	 */
	protected int firstUsed = Constants.BEGINNING;

	/**
	 * Array of the originalSeries' fourier amplitude values.
	 */
	protected double[] fourierAmplitude = null;

	/**
	 * Array of the originalSeries' fourier phase angles in degrees.
	 */
	protected double[] fourierPhaseAngle = null;

	/**
	 * Index (zero-based) into the <code>originalValue</code> array). Indicates
	 * the last originalValue used in forecasting. This allows the user to
	 * reserve actual values for comparison with forecast values.
	 */
	protected int lastUsed = 0;

	/**
	 * Some originalSeries may contain negative or zero values. Such values rule
	 * out log transformation in pursuit of stationary variance. Member
	 * functions <code>doOffset</code> and <code>unDoOffset</code> compute, add,
	 * and subtract the <code>offset</code> originalValue necessary to make all
	 * values positive.
	 * <p>
	 * A originalValue of 0.0 means the originalSeries is not currently offset.
	 */
	double offset = 0.0;

	/**
	 * The original, actual values of the series. This will always contain the
	 * values loaded from a disk file by the user.
	 * <p>
	 * This variable is necessary because the user may choose to trim values
	 * from the beginning or end of the data.
	 */
	protected double[] originalValue = null;

	/**
	 * Array of the originalSeries' partials.
	 */
	protected double[] partial = null;

	/**
	 * A <code>Difference</code> at period between
	 * <code>Constants.MIN_SEASONAL_PERIOD</code> and
	 * <code>Constants.MAX_SEASONAL_PERIOD</code>. Null value indicates no
	 * seasonalization.
	 */
	Difference seasonalization = null;

	/**
	 * Indicates that a log transform has been done to seek stationarity in
	 * variance.
	 */
	boolean transformed = false;

	/**
	 * The working values of the series. This data is the original data (the
	 * data read from disk) as trimmed at the beginning and end, offset,
	 * transformed differenced, and seasonalized by the user.
	 */
	protected double[] workingValue = null;

	/**
	 * Default constructor
	 */
	public Series() {
		super();
		autocorrelation = null;
		autocorrelationConfidenceLimit = 0.0;
		autocovariance = null;
		difference = null;
		firstUsed = Constants.BEGINNING;
		lastUsed = 0;
		offset = 0.0;
		originalValue = null;
		partial = null;
		seasonalization = null;
		transformed = false;
		workingValue = null;
	}

	/**
	 * Constructor with <code>double[]</code> argument. Most attributes are
	 * initialized by <code>setWorkingValue()</code> which is called by
	 * <code>setOriginalValue()</code>. The rationale is that any change to the
	 * original values must be reflected in all other attributes.
	 * 
	 * @param newValue
	 *            The time series of values to be analyzed.
	 */
	public Series(double[] newValue) {
		super();
		difference = null;
		firstUsed = Constants.BEGINNING;
		lastUsed = newValue.length - 1;
		offset = 0.0;
		seasonalization = null;
		transformed = false;
		try {
			setOriginalValue(newValue);
			// Always ignore trim points
			setWorkingValue(newValue);
		} catch (ForecastException ae) {
			ForecastException.handleException(ae);
		}
	}

	/**
	 * Constructor with <code>int</code> argument.
	 * 
	 * @param newLength
	 *            The length of the time series to be analyzed.
	 */
	public Series(int newLength) {
		super();
		difference = new ArrayList<Difference>();
		firstUsed = 0;
		lastUsed = newLength - 1;
		offset = 0.0;
		originalValue = new double[newLength];
		seasonalization = null;
		transformed = false;
	}

	/**
	 * Copy constructor. The only reason for this is to support
	 * <code>equals()</code>. Most attributes are initialized by
	 * <code>setWorkingValue()</code>.
	 * 
	 * @param copiedSeries
	 *            arima.Series
	 */
	public Series(Series copiedSeries) {
		super();
		this.autocorrelation = copiedSeries.autocorrelation;
		this.autocorrelationConfidenceLimit = copiedSeries.autocorrelationConfidenceLimit;
		this.autocovariance = copiedSeries.autocovariance;
		this.firstUsed = copiedSeries.firstUsed;
		this.fourierAmplitude = copiedSeries.fourierAmplitude;
		this.fourierPhaseAngle = copiedSeries.fourierPhaseAngle;
		this.lastUsed = copiedSeries.lastUsed;
		this.partial = copiedSeries.partial;
		setDifference(copiedSeries.getDifference());
		setOffset(copiedSeries.getOffset());
		setSeasonalization(copiedSeries.getSeasonalization());
		setTransformed(copiedSeries.isTransformed());
		try {
			setOriginalValue(copiedSeries.getOriginalValue());
			setWorkingValue(copiedSeries.getWorkingValue());
		} catch (ForecastException ae) {
			ForecastException.handleException(ae);
		}
	}

	/**
	 * Called by several functions to make sure series has enough values to work
	 * with.
	 * 
	 * @param id
	 *            Identifies the caller.
	 * @param doubleArray
	 *            The series array to check.
	 * @return Returns true if there are adequate observations.
	 * @exception ForecastException
	 *                The exception description.
	 */
	protected boolean checkSeries(String id, double[] doubleArray)
			throws ForecastException {
		if (doubleArray == null)
			throw new ForecastException(ID + id
					+ Constants.STRINGS.getString("CheckSeries.errorMessageA"));
		if (doubleArray.length < Constants.MIN_OBSERVATION_COUNT)
			throw new ForecastException(id
					+ Constants.STRINGS.getString("CheckSeries.errorMessageB"));
		return true;
	}

	/**
	 * Compares two objects for equality. Returns a boolean that indicates
	 * whether this object is equivalent to the specified object.
	 * 
	 * @param obj
	 *            the Object to compare with
	 * @return true if these Objects are equal; false otherwise.
	 */
	public boolean equals(Object obj) {
		if (obj == null)
			return false;
		if (!(obj instanceof Series))
			return false;
		Series seriesObject = new Series((Series) obj);
		if (this.autocorrelation == null
				&& seriesObject.autocorrelation != null)
			return false;
		if (this.autocorrelation != null
				&& seriesObject.autocorrelation == null)
			return false;
		if (this.autocorrelation != null
				&& seriesObject.autocorrelation != null) {
			if (!(this.autocorrelation.length == seriesObject.autocorrelation.length))
				return false;
			for (int i = 0; i < this.autocorrelation.length; i++)
				if (!(this.autocorrelation[i] - seriesObject.autocorrelation[i] < Constants.EPSILON))
					return false;
		}
		if (!(this.autocorrelationConfidenceLimit
				- seriesObject.autocorrelationConfidenceLimit < Constants.EPSILON))
			return false;
		if (this.autocovariance == null && seriesObject.autocovariance != null)
			return false;
		if (this.autocovariance != null && seriesObject.autocovariance == null)
			return false;
		if (this.autocovariance != null && seriesObject.autocovariance != null) {
			if (!(this.autocovariance.length == seriesObject.autocovariance.length))
				return false;
			for (int i = 0; i < this.autocovariance.length; i++)
				if (!(this.autocovariance[i] - seriesObject.autocovariance[i] < Constants.EPSILON))
					return false;
		}
		if (this.fourierAmplitude == null
				&& seriesObject.fourierAmplitude != null)
			return false;
		if (this.fourierAmplitude != null
				&& seriesObject.fourierAmplitude == null)
			return false;
		if (this.fourierAmplitude != null
				&& seriesObject.fourierAmplitude != null) {
			if (!(this.fourierAmplitude.length == seriesObject.fourierAmplitude.length))
				return false;
			for (int i = 0; i < this.fourierAmplitude.length; i++)
				if (!(this.fourierAmplitude[i]
						- seriesObject.fourierAmplitude[i] < Constants.EPSILON))
					return false;
		}
		if (this.fourierPhaseAngle == null
				&& seriesObject.fourierPhaseAngle != null)
			return false;
		if (this.fourierPhaseAngle != null
				&& seriesObject.fourierPhaseAngle == null)
			return false;
		if (this.fourierPhaseAngle != null
				&& seriesObject.fourierPhaseAngle != null) {
			if (!(this.fourierPhaseAngle.length == seriesObject.fourierPhaseAngle.length))
				return false;
			for (int i = 0; i < this.fourierPhaseAngle.length; i++)
				if (!(this.fourierPhaseAngle[i]
						- seriesObject.fourierPhaseAngle[i] < Constants.EPSILON))
					return false;
		}
		if (this.partial == null && seriesObject.partial != null)
			return false;
		if (this.partial != null && seriesObject.partial == null)
			return false;
		if (this.partial != null && seriesObject.partial != null) {
			if (!(this.partial.length == seriesObject.partial.length))
				return false;
			for (int i = 0; i < this.partial.length; i++)
				if (!(this.partial[i] - seriesObject.partial[i] < Constants.EPSILON))
					return false;
		}
		if (this.originalValue == null && seriesObject.originalValue != null)
			return false;
		if (this.originalValue != null && seriesObject.originalValue == null)
			return false;
		if (this.originalValue != null && seriesObject.originalValue != null) {
			if (!(this.originalValue.length == seriesObject.originalValue.length))
				return false;
			for (int i = 0; i < this.originalValue.length; i++)
				if (!(this.originalValue[i] - seriesObject.originalValue[i] < Constants.EPSILON))
					return false;
		}
		if (this.difference == null && seriesObject.difference != null)
			return false;
		else if (this.difference != null && seriesObject.difference == null)
			return false;
		if (this.difference != null && seriesObject.difference != null) {
			if (!(this.difference.size() == seriesObject.difference.size()))
				return false;
			Iterator<Difference> thisIterator = this.getDifference().iterator();
			Iterator<Difference> TSOIterator = seriesObject.getDifference()
					.iterator();
			while (thisIterator.hasNext() && TSOIterator.hasNext()) {
				if (!thisIterator.next().equals(TSOIterator.next()))
					return false;
			}
		}
		if (this.offset != seriesObject.offset)
			return false;
		if (this.transformed != seriesObject.transformed)
			return false;
		return true;
	}

	/**
	 * Computes the fourier power spectrum of <code>workingValue</code>. Adapted
	 * from BASIC routine in Makridakis, Wheelwright, and McGee,
	 * "Forecasting: Methods and Applications" (2nd ed., New York: John Wiley &
	 * Sons, 1983), p. 404.
	 * 
	 * @throws ForecastException
	 */
	protected final void fourier() throws ForecastException {
		final int t = workingValue.length; // Number of data points
		final int p1 = (t - 1) / 2; // Max frequency allowed
		fourierAmplitude = new double[p1];
		fourierPhaseAngle = new double[p1];
		final double c1 = Math.cos(2 * Math.PI / t);
		final double s1 = Math.sin(2 * Math.PI / t);
		double a = 0.0;
		double b = 0.0;
		double c = 1.0;
		double q = 0.0;
		double s = 0.0;
		double t2 = 0.0;
		double u = 0.0;
		double u1 = 0.0;
		double u2 = 0.0;
		for (int p = 0; p <= p1; p++) {
			u1 = 0.0;
			u2 = 0.0;
			for (int k = t; k > 1; k--) {
				u = getElementAt(k - 1, workingValue) + 2 * c * u1 - u2;
				u2 = u1;
				u1 = u;
			}
			a = 2.0 / t * (getElementAt(0, workingValue) + c * u1 - u2);
			b = 2.0 / t * s * u1;
			if (p <= 0) {
				a /= 2.0;
			}
			if (p != 0) {
				setFourierAmplitudeAt(p - 1, Math.sqrt(a * a + b * b));
				t2 = Math.abs(a / b);
				t2 = Constants.RADIAN_FACTOR * Math.atan(t2);
				if (b <= 0.0 && a > 0.0)
					t2 = 180 - t2;
				if (b <= 0.0 && a <= 0.0)
					t2 += 180.0;
				if (b > 0.0 && a <= 0.0)
					t2 = 360.0 - t2;
				setFourierPhaseAngleAt(p - 1, t2);
			}
			q = c1 * c - s1 * s;
			s = c1 * s + s1 * c;
			c = q;
		}
	}

	/**
	 * Gets an element of the fourier amplitude vector.
	 * 
	 * @param index
	 *            The index at which to get fourier amplitude.
	 * @return The value of the amplitude at <code>index</code>.
	 * @throws ForecastException
	 */
	public double getAmplitudeAt(int index) throws ForecastException {
		String id = "getAmplitudeAt(int): ";
		int maxIndex = fourierAmplitude.length - 1;
		if (index > maxIndex)
			throw new ForecastException(ID + id
					+ Constants.STRINGS.getString("GetElement.errorMessageA"));
		return fourierAmplitude[index];
	}

	/**
	 * Returns the originalSeries' autocorrelation array.
	 * 
	 * @return double[]
	 */
	public double[] getAutocorrelation() {
		return autocorrelation;
	}

	/**
	 * Returns the originalValue from the autocorrelation array at the given
	 * index.
	 * 
	 * @return double
	 * @param index
	 *            int
	 * @exception ForecastException
	 *                The exception description.
	 */
	public double getAutocorrelationAt(int index) throws ForecastException {
		String id = "getAutocorrelation(int)::Series---";

		int maxIndex = autocorrelation.length - 1;
		if (index > maxIndex)
			throw new ForecastException(id + " "
					+ Constants.STRINGS.getString("`lation.requestedIndex")
					+ "; " + index + "; "
					+ Constants.STRINGS.getString("Autocorrelation.maxIndex")
					+ " " + maxIndex);
		return autocorrelation[index];
	}

	/**
	 * Returns the originalSeries' autocorrelation confidence limit.
	 * 
	 * @return double
	 */
	public double getAutocorrelationConfidenceLimit() {
		return autocorrelationConfidenceLimit;
	}

	/**
	 * Returns the originalSeries' autocovariance array.
	 * 
	 * @return double[]
	 */
	public double[] getAutocovariance() {
		return autocovariance;
	}

	/**
	 * Returns the differencing <code>ArrayList</code>.
	 * 
	 * @return ArrayList
	 */
	public ArrayList<Difference> getDifference() {
		return difference;
	}

	/**
	 * Returns the series array value at the given index, where the index is
	 * zero-based.
	 * 
	 * @return double
	 * @param index
	 *            The index of the value to return.
	 * @param doubleArray
	 *            The array of values containing the desired element.
	 * @exception ForecastException
	 *                Thrown if the requested index is out of bounds.
	 */
	public double getElementAt(int index, double[] doubleArray)
			throws ForecastException {
		String id = "getElement(int): ";
		checkSeries(id, doubleArray);
		if (index >= doubleArray.length || index < 0)
			throw new ForecastException(id
					+ Constants.STRINGS.getString("GetElement.errorMessageA"));
		return doubleArray[index];
	}

	/**
	 * @return Returns the firstUsed.
	 */
	public int getFirstUsed() {
		return firstUsed;
	}

	/**
	 * Gets the fourier amplitude array.
	 * 
	 * @return double[]
	 */
	public double[] getFourierAmplitude() {
		return fourierAmplitude;
	}

	/**
	 * Gets fourierPhaseAngle.
	 * 
	 * @return Returns the fourierPhaseAngle.
	 */
	public double[] getFourierPhaseAngle() {
		return fourierPhaseAngle;
	}

	/**
	 * @return Returns the lastUsed.
	 */
	public int getLastUsed() {
		return lastUsed;
	}

	/**
	 * Returns the number of observations in the originalSeries.
	 * 
	 * @return int
	 */
	public int getLength() {
		return originalValue.length;
	}

	/**
	 * Returns the arithmetic mean of <code>double[]</code>
	 * 
	 * @return double
	 * @param doubleArray
	 *            The array for the desired mean.
	 * @exception ForecastException
	 *                Thrown if the array is too short.
	 */
	public double getMean(double[] doubleArray) throws ForecastException {
		double sum = 0.0;

		checkSeries("getMean(): ", doubleArray);
		for (int i = 0; i < doubleArray.length; i++)
			sum += doubleArray[i];
		return sum / doubleArray.length;
	}

	/**
	 * @return Returns the offset.
	 */
	public double getOffset() {
		return offset;
	}

	/**
	 * Returns the array of time series original values.
	 * 
	 * @return double[]
	 */
	public double[] getOriginalValue() {
		return originalValue;
	}

	/**
	 * Returns the array of the originalSeries' partials.
	 * 
	 * @return double[]
	 */
	public double[] getPartial() {
		return partial;
	}

	/**
	 * @return Returns the <code>seasonalization</code>.
	 */
	public Difference getSeasonalization() {
		return seasonalization;
	}

	/**
	 * Returns the variance of a <code>double[]</code>.
	 * 
	 * @return double
	 * @param doubleArray
	 *            The array of doubles for the desired variance.
	 * @exception ForecastException
	 *                Thrown for arrays which ar too small.
	 */
	public double getVariance(double[] doubleArray) throws ForecastException {
		double mean = 0.0;
		double sum = 0.0;

		checkSeries("getVariance(): ", doubleArray);
		mean = getMean(doubleArray);
		for (int i = 0; i < doubleArray.length; i++)
			sum += Math.pow((doubleArray[i] - mean), 2);
		return sum / (doubleArray.length - 1);
	}

	/**
	 * Returns the array of time series working values, those remaining after
	 * any trimming at beginning or end of <code>originalValue</code>.
	 * 
	 * @return Returns the workingValue.
	 */
	public double[] getWorkingValue() {
		return workingValue;
	}

	/**
	 * @return Returns the transformed.
	 */
	public boolean isTransformed() {
		return transformed;
	}

	/**
	 * Non-recursive computation of partial autocorrelation function per
	 * Bowerman & O'Connell, pp. 355ff.
	 * 
	 * @return The partial autocorrelation values as an array of
	 *         <code>double</code>.
	 * @throws ForecastException
	 */
	protected final double[] rkk() throws ForecastException {
		double[] partials = new double[Constants.MAX_LAG]; // 0-based
		// the r[][] array has 0-lag at index 0
		double[][] r = new double[Constants.MAX_LAG + 1][Constants.MAX_LAG + 1];
		double sum1 = 0.0;
		double sum2 = 0.0;
		int j, k;

		for (k = 1; k <= Constants.MAX_LAG; k++) {
			if (k == 1) {
				r[k][k] = getAutocorrelationAt(1);
			} else {
				sum1 = 0.0;
				sum2 = 0.0;
				for (j = 1; j <= k - 1; j++) {
					sum1 += r[k - 1][j] * getAutocorrelationAt(k - j);
					sum2 += r[k - 1][j] * getAutocorrelationAt(j);
				}
				r[k][k] = (getAutocorrelationAt(k) - sum1) / (1.0 - sum2);
				for (j = 1; j <= k - 1; j++)
					r[k][j] = r[k - 1][j] - r[k][k] * r[k - 1][k - j];
			}
		}
		for (k = 0; k < Constants.MAX_LAG; k++) {
			partials[k] = r[k][k];
		}
		return partials;
	}

	/**
	 * Sets the series' differencing <code>ArrayList</code>. Called only by the
	 * copy constructor.
	 * 
	 * @param newDifference
	 *            The <code>difference</code> of the copied series.
	 */
	private void setDifference(ArrayList<Difference> newDifference) {
		if (newDifference == null)
			difference = null;
		else {
			difference = new ArrayList<Difference>();
			Iterator<Difference> iterator = newDifference.iterator();
			while (iterator.hasNext()) {
				difference.add(iterator.next());
			}
		}
	}

	/**
	 * Sets the <code>double[]</code> value at the given index, where the index
	 * is zero-based.
	 * 
	 * @param index
	 *            The index of the value to set.
	 * @param newValue
	 *            The new value to set.
	 * @param doubleArray
	 *            The array containing the value to set.
	 * @exception ForecastException
	 *                The exception description.
	 */
	protected void setElement(int index, double newValue, double[] doubleArray)
			throws ForecastException {
		String id = "setElement(int,double, double[]): ";

		checkSeries(id, doubleArray);
		if (index >= originalValue.length || index < 0)
			throw new ForecastException(ID + id
					+ Constants.STRINGS.getString("SetElement.errorMessageA"));
		doubleArray[index] = newValue;
	}

	/**
	 * @param newFirstUsed
	 *            The firstUsed to set.
	 */
	public void setFirstUsed(int newFirstUsed) {
		this.firstUsed = newFirstUsed;
	}

	/**
	 * Sets an element of the fourier array at the index. Called only by the
	 * <code>setValue</code> method.
	 * 
	 * @param newIndex
	 *            The position in the array at which to set the value.
	 * @param newValue
	 *            The value to set at <code>index</code>.
	 * @throws ForecastException
	 */
	protected void setFourierAmplitudeAt(int newIndex, double newValue)
			throws ForecastException {
		String id = "setFourierAmplitudeAt(int, double): ";

		int maxIndex = fourierAmplitude.length - 1;
		if (newIndex > maxIndex)
			throw new ForecastException(id
					+ Constants.STRINGS.getString("Fourier.setIndex") + " "
					+ newIndex + "; "
					+ Constants.STRINGS.getString("Autocorrelation.maxIndex")
					+ " " + maxIndex);
		fourierAmplitude[newIndex] = newValue;
	}

	/**
	 * Sets an element of the fourier originalSeries phase angle array at the
	 * index. Called only by the <code>setValue</code> method.
	 * 
	 * @param newIndex
	 *            The position in the array at which to set the value.
	 * @param newValue
	 *            The value to set at <code>index</code>.
	 * @throws ForecastException
	 */
	protected void setFourierPhaseAngleAt(int newIndex, double newValue)
			throws ForecastException {
		String id = "setFourierPhaseAngleAt(int, double): ";
		int maxIndex = fourierPhaseAngle.length - 1;
		if (newIndex > maxIndex)
			throw new ForecastException(id
					+ Constants.STRINGS.getString("Fourier.setIndex") + " "
					+ newIndex
					+ Constants.STRINGS.getString("Autocorrelation.maxIndex")
					+ " " + maxIndex);
		fourierPhaseAngle[newIndex] = newValue;
	}

	/**
	 * @param newLastUsed
	 *            The lastUsed to set.
	 */
	public void setLastUsed(int newLastUsed) {
		this.lastUsed = newLastUsed;
	}

	/**
	 * Sets <code>offset</code>.
	 * 
	 * @param newOffset
	 *            The offset to set.
	 */
	public void setOffset(double newOffset) {
		offset = newOffset;
	}

	/**
	 * Sets the <code>originalValue</code> to a new values.
	 * 
	 * @param newValue
	 *            The array of values to set.
	 */
	public void setOriginalValue(double[] newValue) {
		if (newValue == null) {
			originalValue = null;
			return;
		}
		int newValueLength = newValue.length;
		originalValue = new double[newValueLength];
		for (int i = 0; i < newValueLength; i++)
			originalValue[i] = newValue[i];
	}

	/**
	 * Sets the <code>seasonalization</code>.
	 * 
	 * @param newSeasonalization
	 *            The <code>seasonalization</code> to set.
	 */
	public void setSeasonalization(Difference newSeasonalization) {
		if (newSeasonalization == null)
			seasonalization = null;
		else
			seasonalization = new Difference(newSeasonalization);
	}

	/**
	 * Sets the <code>transformed</code> attribute.
	 * 
	 * @param newTransformed
	 *            The transformed to set.
	 */
	public void setTransformed(boolean newTransformed) {
		transformed = newTransformed;
	}

	/**
	 * Sets the working values array. Everytime this is done, many other
	 * <code>Series</code> attributes must be set or updated.
	 * <p>
	 * The array is always set on the basis of the entire <code>double[]</code>
	 * argument. If trimming of the original series is desired, this must be
	 * reflected in the argument.
	 * 
	 * @param newValue
	 *            The workingValue to set.
	 * @throws ForecastException
	 */
	public void setWorkingValue(double[] newValue) throws ForecastException {
		if (newValue == null) {
			workingValue = null;
			return;
		}
		int newValueLength = newValue.length;
		workingValue = new double[newValueLength];
		for (int i = 0; i < newValueLength; i++)
			workingValue[i] = newValue[i];
		autocovariance = new double[Constants.MAX_LAG + 1];
		autocorrelation = new double[Constants.MAX_LAG + 1];
		partial = new double[Constants.MAX_LAG + 1];
		if (newValueLength < Constants.MIN_OBSERVATION_COUNT)
			throw new ForecastException(
					"Series::setWorkingValue(double[])---"
							+ Constants.STRINGS
									.getString("Autocorrelation.errorMessageA")
							+ newValueLength
							+ Constants.STRINGS
									.getString("Autocorrelation.errorMessageB")
							+ Constants.MIN_OBSERVATION_COUNT);
		double sum1 = 0.0;
		for (int i = 0; i < newValueLength; i++)
			sum1 += newValue[i];
		double muHat = sum1 / newValueLength;
		// Compute autocovariance, autocorrelation, and partial autocorrelation
		for (int k = 0; k <= Constants.MAX_LAG; k++) {
			sum1 = 0.0;
			for (int t = 0; t < newValueLength - k; t++) {
				sum1 += ((newValue[t] - muHat) * (newValue[t + k] - muHat));
			}
			autocovariance[k] = sum1 / newValueLength;
			autocorrelation[k] = autocovariance[k] / autocovariance[0];
		}
		partial = rkk();
		autocorrelationConfidenceLimit = Constants.CONFIDENCE_FACTOR
				/ Math.sqrt(newValueLength);
		// Compute fourier power spectrum
		fourier();
	}

	/**
	 * Returns a subset of a <code>double[]</code>, from beginning index through
	 * ending index.
	 * 
	 * @return double[]
	 * @param begin
	 *            Index of the first member of the subset.
	 * @param end
	 *            Index of the last member of the subset.
	 * @param doubleArray
	 *            The array to be subset.
	 * @exception ForecastException
	 *                The exception description.
	 */
	public double[] subset(int begin, int end, double[] doubleArray)
			throws ForecastException {
		checkSeries("subset(int,int): ", doubleArray);
		int newLength = end - begin + 1;
		double[] newValue = new double[newLength];
		for (int i = 0; i < newLength; i++)
			newValue[i] = getElementAt(i + begin, doubleArray);
		return newValue;
	}

	/**
	 * Returns a String that represents the original values of this object.
	 * 
	 * @return a string representation of the receiver
	 */
	public String toString() {
		int i;
		StringBuffer stringBuffer = new StringBuffer();
		// Change commenting for desired output
		// if (originalValue != null)
		// stringBuffer.append("Original value array has " +
		// originalValue.length + " elements\n");
		// else
		// stringBuffer.append("Original value array is null\n");
		if (originalValue != null) {
			for (i = 0; i < originalValue.length; i++)
				stringBuffer.append(originalValue[i]).append("\n");
		}
		// else
		// stringBuffer.append("No original data set\n");
		// Change commenting for desired output
		// if (workingValue == null)
		// stringBuffer.append("Working value array is null\n");
		// else
		// stringBuffer.append("Working value array has " + workingValue.length
		// + " elements\n");
		// if (this.difference == null)
		// stringBuffer.append("No differencing\n");
		// else
		// stringBuffer.append("Differencing order = " + difference.size()
		// + "\n");
		// stringBuffer.append("Offset = " + offset + "\n");
		// if (seasonalization == null)
		// stringBuffer.append("No seasonalization\n");
		// else
		// stringBuffer
		// .append("Seasonalized at order "
		// + (seasonalization.getInitialValue().length - seasonalization
		// .getValue().length) + "\n");
		// stringBuffer.append("Series is ");
		// if (!transformed)
		// stringBuffer.append("not ");
		// stringBuffer.append("transformed\n");
		return stringBuffer.toString();
	}
}